/*
 * Copyright (c) 2012 Mateusz Piękos
 * Licensed under the simplified BSD license.
 * See Documentation/Licenses/BSD-simplified.txt for more information.
 */

#include "GView.h"
#include <QtSwiftUtil.h>

namespace Swift {
	GView::GView(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent), zValue(0), mousePressed(false), brush(QColor(Qt::white)), defaultBrush(QColor(Qt::white)), mode(GView::Select), lastItem(NULL), selectionRect(NULL), textDialog(NULL) {
	}

	void GView::setLineWidth(int i) {
		pen.setWidth(i);
		if (selectionRect) {
			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
			changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush);
			lastItemChanged(item, items_.indexOf(item)+1, Update);
		} else {
			defaultPen.setWidth(i);
		}
	}

	void GView::setLineColor(QColor color) {
		pen.setColor(color);
		if (selectionRect) {
			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
			changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush);
			lastItemChanged(item, items_.indexOf(item)+1, Update);
		} else {
			defaultPen.setColor(color);
		}
		lineColorChanged(color);
	}

	QColor GView::getLineColor() {
		return pen.color();
	}

	void GView::setBrushColor(QColor color) {
		brush.setColor(color);
		if (selectionRect) {
			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
			changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush);
			lastItemChanged(item, items_.indexOf(item)+1, Update);
		} else {
			defaultBrush.setColor(color);
		}
		brushColorChanged(color);
	}

	QColor GView::getBrushColor() {
		return brush.color();
	}

	void GView::setMode(Mode mode) {
		this->mode = mode;
		lastItem = 0;
		deselect();
	}

	void GView::addItem(QGraphicsItem* item, QString id, int pos) {
		itemsMap_.insert(id, item);
		if (pos > items_.size()) {
			item->setZValue(zValue++);
			scene()->addItem(item);
			items_.append(item);
		} else {
			QGraphicsItem* temp = items_.at(pos-1);
			item->setZValue(temp->zValue());
			scene()->addItem(item);
			item->stackBefore(temp);
			items_.insert(pos-1, item);
		}
	}

	void GView::clear() {
		scene()->clear();
		items_.clear();
		itemsMap_.clear();
		lastItem = 0;
		selectionRect = 0;
		brush = QBrush(QColor(Qt::white));
		defaultBrush = QBrush(QColor(Qt::white));
		pen = QPen();
		pen.setWidth(1);
		defaultPen = pen;
		lineWidthChanged(1);
		lineColorChanged(pen.color());
		brushColorChanged(brush.color());
	}

	QGraphicsItem* GView::getItem(QString id) {
		return itemsMap_.value(id);
	}

	void GView::deleteItem(QString id) {
		deselect(id);
		QGraphicsItem* item = itemsMap_.value(id);
		items_.removeOne(item);
		itemsMap_.remove(id);
		scene()->removeItem(item);
		delete item;
	}

	QString GView::getNewID() {
		return P2QSTRING(idGenerator.generateID());
	}

	void GView::mouseMoveEvent(QMouseEvent* event)
	{
		if (!mousePressed) {
			return;
		}

		if (mode == Line) {
			QGraphicsLineItem* item = qgraphicsitem_cast<QGraphicsLineItem*>(lastItem);
			if(item != 0) {
				QLineF line = item->line();
				line.setP1(this->mapToScene(event->pos()));
				item->setLine(line);

			}
		}
		else if (mode == Rect) {
			QGraphicsRectItem* item = qgraphicsitem_cast<QGraphicsRectItem*>(lastItem);
			if (item != 0) {
				QPointF beginPoint = item->data(0).toPointF();
				QPointF newPoint = this->mapToScene(event->pos());
				QRectF rect = item->rect();
				if (beginPoint.x() <= newPoint.x() && beginPoint.y() <= newPoint.y()) {
					rect.setTopLeft(beginPoint);
					rect.setBottomRight(newPoint);
				}
				else if (beginPoint.x() > newPoint.x() && beginPoint.y() <= newPoint.y()) {
					rect.setTopRight(beginPoint);
					rect.setBottomLeft(newPoint);
				}
				else if (beginPoint.x() <= newPoint.x() && beginPoint.y() > newPoint.y()) {
					rect.setBottomLeft(beginPoint);
					rect.setTopRight(newPoint);
				}
				else if (beginPoint.x() > newPoint.x() && beginPoint.y() > newPoint.y()) {
					rect.setBottomRight(beginPoint);
					rect.setTopLeft(newPoint);
				}
				item->setRect(rect); 
			}
		}
		else if (mode == Circle) {
			QGraphicsEllipseItem* item = qgraphicsitem_cast<QGraphicsEllipseItem*>(lastItem);
			QPainterPath path;
			QPointF beginPoint = item->data(0).toPointF();
			QPointF newPoint = this->mapToScene(event->pos());
			QRectF rect = item->rect();
			if (beginPoint.x() <= newPoint.x() && beginPoint.y() <= newPoint.y()) {
				rect.setTopLeft(beginPoint);
				rect.setBottomRight(newPoint);
			}
			else if (beginPoint.x() > newPoint.x() && beginPoint.y() <= newPoint.y()) {
				rect.setTopRight(beginPoint);
				rect.setBottomLeft(newPoint);
			}
			else if (beginPoint.x() <= newPoint.x() && beginPoint.y() > newPoint.y()) {
				rect.setBottomLeft(beginPoint);
				rect.setTopRight(newPoint);
			}
			else if (beginPoint.x() > newPoint.x() && beginPoint.y() > newPoint.y()) {
				rect.setBottomRight(beginPoint);
				rect.setTopLeft(newPoint);
			}

			item->setRect(rect);
		}
		else if (mode == HandLine) {
			FreehandLineItem* item  = qgraphicsitem_cast<FreehandLineItem*>(lastItem);
			if (item != 0) {
				QPointF newPoint = this->mapToScene(event->pos());
				item->lineTo(newPoint);
			}
		}
		else if (mode == Polygon) {
			QGraphicsPolygonItem* item = qgraphicsitem_cast<QGraphicsPolygonItem*>(lastItem);
			QPointF newPoint = this->mapToScene(event->pos());
			QPolygonF polygon = item->polygon();
			polygon.erase(polygon.end()-1);
			polygon.append(newPoint);
			item->setPolygon(polygon);
		}
		else if (mode == Select) {
			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
			if (item != 0) {
				QPainterPath path;
				QPointF beginPoint = selectionRect->data(0).toPointF();
				QPointF newPoint = this->mapToScene(event->pos());
				item->setPos(beginPoint + newPoint);
				selectionRect->setPos(beginPoint + newPoint);
			}
		}
	}

	void GView::mousePressEvent(QMouseEvent *event)
	{
		mousePressed = true;
		deselect();
		if (mode == Line) {
			QPointF point = this->mapToScene(event->pos());
			QGraphicsItem* item = scene()->addLine(point.x(), point.y(), point.x(), point.y(), pen);
			QString id = getNewID();
			item->setZValue(10000000);
			item->setData(100, id);
			item->setData(101, items_.size());
			lastItem = item;
		}
		else if (mode == Rect) {
			QPointF point = this->mapToScene(event->pos());
			QGraphicsRectItem* item = scene()->addRect(point.x(), point.y(), 0, 0, pen, brush);
			QString id = getNewID();
			item->setZValue(10000000);
			item->setData(0, point);
			item->setData(100, id);
			item->setData(101, items_.size());
			lastItem = item;
		}
		else if (mode == Rubber) {
			QPointF point = this->mapToScene(event->pos());
			int w = pen.width();
			QRectF rect(point.x()-w, point.y()-w, w*2, w*2);
			QList<QGraphicsItem*> list = scene()->items(rect);
			if (!list.isEmpty())
			{
				QGraphicsItem* item = scene()->items(rect).first();
				QString id = item->data(100).toString();
				int pos = items_.indexOf(item)+1;
				itemDeleted(id, pos);
				deleteItem(id);
			}
		}
		else if (mode == Circle) {
			QPointF point = this->mapToScene(event->pos());
			QGraphicsEllipseItem* item = scene()->addEllipse(point.x(), point.y(), 0, 0, pen, brush);
			QString id = getNewID();
			item->setZValue(10000000);
			item->setData(0, point);
			item->setData(100, id);
			item->setData(101, items_.size());
			lastItem = item;
		}
		else if (mode == HandLine) {
			QPointF point = this->mapToScene(event->pos());
			FreehandLineItem* item = new FreehandLineItem;
			QString id = getNewID();
			item->setPen(pen);
			item->setStartPoint(point);
			item->setZValue(10000000);
			item->setData(100, id);
			item->setData(101, items_.size());
			scene()->addItem(item);
			lastItem = item;
		}
		else if (mode == Text) {
			QPointF point = this->mapToScene(event->pos());
			QGraphicsTextItem* item = scene()->addText("");
			QString id = getNewID();
			item->setData(100, id);
			item->setData(101, items_.size());
			item->setDefaultTextColor(pen.color());
			textDialog = new TextDialog(item, this);
			connect(textDialog, SIGNAL(accepted(QGraphicsTextItem*)), this, SLOT(handleTextItemModified(QGraphicsTextItem*)));
			textDialog->setAttribute(Qt::WA_DeleteOnClose);
			textDialog->show();
			item->setPos(point);
			lastItem = item;
		}
		else if (mode == Polygon) {
			QPointF point = this->mapToScene(event->pos());
			QGraphicsPolygonItem* item = dynamic_cast<QGraphicsPolygonItem*>(lastItem);
			if (item == 0) {
				QPolygonF polygon;
				polygon.append(point);
				polygon.append(point);
				item = scene()->addPolygon(polygon, pen, brush);
				QString id = getNewID();
				item->setZValue(10000000);
				item->setData(100, id);
				item->setData(101, items_.size());
				lastItem = item;
			}
			else {
				QPolygonF polygon;
				polygon = item->polygon();
				polygon.append(point);
				item->setPolygon(polygon);
			}
		}
		else if (mode == Select) {
			QPointF point = this->mapToScene(event->pos());
			int w = pen.width();
			if (w == 0) {
				w = 1;
			}
			QRectF rect(point.x()-w, point.y()-w, w*2, w*2);
			QList<QGraphicsItem*> list = scene()->items(rect);
			if (!list.isEmpty()) {
				QPen pen;
				pen.setColor(QColor(Qt::gray));
				pen.setStyle(Qt::DashLine);
				QGraphicsItem *item = scene()->items(rect).first();
				selectionRect = scene()->addRect(item->boundingRect(), pen);
				selectionRect->setZValue(1000000);
				selectionRect->setData(0, item->pos()-point);
				selectionRect->setPos(item->pos());
				QVariant var(QVariant::UserType);
				var.setValue(item);
				selectionRect->setData(1, var);
				setActualPenAndBrushFromItem(item);
			}
		}
	}

	void GView::mouseReleaseEvent(QMouseEvent* /*event*/)
	{
		mousePressed = false;
		QGraphicsPolygonItem* polygon = dynamic_cast<QGraphicsPolygonItem*>(lastItem);
		if (polygon && polygon->polygon().size() >= 3) {
			lastItemChanged(polygon, items_.indexOf(polygon)+1, Update);
		} else if (lastItem) {
			zValue++;
			lastItem->setZValue(zValue++);
			items_.append(lastItem);
			itemsMap_.insert(lastItem->data(100).toString(), lastItem);

			lastItemChanged(lastItem, items_.size(), New);
		} else if (selectionRect){
			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
			lastItemChanged(item, items_.indexOf(item)+1, Update);
		}
	}


	void GView::handleTextItemModified(QGraphicsTextItem* item) {
		lastItemChanged(item, item->data(101).toInt(), Update);
	}

	void GView::moveUpSelectedItem()
	{
		if (selectionRect) {
			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
			int pos = items_.indexOf(item);
			if (pos < items_.size()-1) {
				lastItemChanged(item, pos+1, MoveUp);
				move(item, pos+2);
			}
		}
	}

	void GView::moveDownSelectedItem()
	{
		if (selectionRect) {
			QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>();
			int pos = items_.indexOf(item);
			if (pos > 0) {
				lastItemChanged(item, pos+1, MoveDown);
				move(item, pos);
			} 
		}
	}

	void GView::move(QGraphicsItem* item, int npos) {
		int pos = items_.indexOf(item);
		QGraphicsItem* itemAfter = NULL;
		if (npos-1 > pos) {
			if (npos == items_.size()) {
				item->setZValue(zValue++);
			} else {
				itemAfter = items_.at(npos);
			}

			items_.insert(npos, item); 
			items_.removeAt(pos);
		} else if (npos-1 < pos) {
			itemAfter = items_.at(npos-1);
			items_.insert(npos-1, item);
			items_.removeAt(pos+1);
		}
		if (itemAfter) {
			item->setZValue(itemAfter->zValue());
			item->stackBefore(itemAfter);
		}
	}

	void GView::changePenAndBrush(QGraphicsItem* item, QPen pen, QBrush brush) {
		QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item);
		if (lineItem) {
			lineItem->setPen(pen);
		}

		FreehandLineItem* handLineItem = qgraphicsitem_cast<FreehandLineItem*>(item);
		if (handLineItem) {
			handLineItem->setPen(pen);
		}

		QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item);
		if (rectItem) {
			rectItem->setPen(pen);
			rectItem->setBrush(brush);
		}

		QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item);
		if (textItem) {
			textItem->setDefaultTextColor(pen.color());
		}

		QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item);
		if (polygonItem) {
			polygonItem->setPen(pen);
			polygonItem->setBrush(brush);
		}

		QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
		if (ellipseItem) {
			ellipseItem->setPen(pen);
			ellipseItem->setBrush(brush);
		}
		lineColorChanged(pen.color());
		brushColorChanged(brush.color());
	}

	void GView::setActualPenAndBrushFromItem(QGraphicsItem* item) {
		QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item);
		if (lineItem) {
			pen = lineItem->pen();
		}

		FreehandLineItem* handLineItem = qgraphicsitem_cast<FreehandLineItem*>(item);
		if (handLineItem) {
			pen = handLineItem->pen();
		}

		QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item);
		if (rectItem) {
			pen = rectItem->pen();
			brush = rectItem->brush();
		}

		QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item);
		if (textItem) {
			pen.setColor(textItem->defaultTextColor());
		}

		QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item);
		if (polygonItem) {
			pen = polygonItem->pen();
			brush = polygonItem->brush();
		}

		QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item);
		if (ellipseItem) {
			pen = ellipseItem->pen();
			brush = ellipseItem->brush();
		}
		lineWidthChanged(pen.width());
		lineColorChanged(pen.color());
		brushColorChanged(brush.color());
	}

	void GView::deselect() {
		if (selectionRect != 0)	{
			pen = defaultPen;
			brush = defaultBrush;
			scene()->removeItem(selectionRect);
			delete selectionRect;
			selectionRect = 0;
			lineWidthChanged(pen.width());
			lineColorChanged(pen.color());
			brushColorChanged(brush.color());
		}
	}

	void GView::deselect(QString id) {
		if (selectionRect != 0)	{
			QGraphicsItem* item = getItem(id);
			if (item && selectionRect->data(1).value<QGraphicsItem*>() == item) {
				pen = defaultPen;
				brush = defaultBrush;
				scene()->removeItem(selectionRect);
				delete selectionRect;
				selectionRect = 0;
				lineWidthChanged(pen.width());
				lineColorChanged(pen.color());
				brushColorChanged(brush.color());
			}
		}
	}
}
